package com.nulldev.util.uri.yt;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.nulldev.util.java.DataSet;
import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonArray;
import com.nulldev.util.lang.json.JsonObject;
import com.nulldev.util.lang.json.JsonValue;
import com.nulldev.util.lang.xml.XML;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.CharsetUtil;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.URIUtil2.RandomUserAgent;

public class MetadataExtractor {
	private static final Pattern patYouTubePageLink = Pattern.compile("(http|https)://(www\\.|m.|)youtube\\.com/watch\\?v=(.+?)( |\\z|&)");
    private static final Pattern patYouTubeShortLink = Pattern.compile("(http|https)://(www\\.|)youtu.be/(.+?)( |\\z|&)");
	
    private static final Pattern patTitle = Pattern.compile("title=(.*?)(&|\\z)");
    private static final Pattern patAuthor = Pattern.compile("author=(.+?)(&|\\z)");
    private static final Pattern patChannelId = Pattern.compile("ucid=(.+?)(&|\\z)");
    private static final Pattern patLength = Pattern.compile("length_seconds=(\\d+?)(&|\\z)");
    private static final Pattern patViewCount = Pattern.compile("view_count=(\\d+?)(&|\\z)");
    private static final Pattern patHlsvp = Pattern.compile("hlsvp=(.+?)(&|\\z)");
    private static final Pattern patAvgRating = Pattern.compile("avg_rating=(.*?)(&|\\z)");
    private static final Pattern patTags = Pattern.compile("(?s)(?<=keywords=).*?(?=&)");
    
	public static String getVideoID(final String ytUrl) {
		String videoID = null;
		Matcher mat = patYouTubePageLink.matcher(ytUrl);
        if (mat.find()) {
            videoID = mat.group(3);
        } else {
            mat = patYouTubeShortLink.matcher(ytUrl);
            if (mat.find()) {
                videoID = mat.group(3);
            } else if (ytUrl.matches("\\p{Graph}+?")) {
                videoID = ytUrl;
            }
        }
        return videoID;
	}
	
	public static VideoMeta getMeta(final URL url) throws Exception {
		final String videoID = getVideoID(url.toString().replaceAll(Pattern.quote("hooktube.com"), "youtube.com"));
		String ytInfoUrl = "https://";
        ytInfoUrl += "www.youtube.com/get_video_info?video_id=" + videoID + "&eurl="
                + URLEncoder.encode("https://youtube.googleapis.com/v/" + videoID, "UTF-8");
        
        String streamMap;
        BufferedReader reader = null;
        URL getUrl = new URL(ytInfoUrl);
        HttpURLConnection urlConnection = (HttpURLConnection) getUrl.openConnection();
        urlConnection.setRequestProperty("User-Agent", RandomUserAgent.getRandomUserAgent());
        try {
            reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            streamMap = reader.readLine();
            //System.out.println(streamMap);
        }catch (Exception e) {
        	throw new RuntimeException(e);
        } finally {
            if (reader != null)
                reader.close();
            urlConnection.disconnect();
        }
        
        return getMeta(streamMap, videoID);
	}
	
	private static VideoMeta getMeta(final String getVideoInfo, final String videoID) throws UnsupportedEncodingException {
		boolean isLiveStream = false;
        String title = null, author = null, channelId = null;
        long viewCount = 0, length = 0;
        double avgRating = 0.0;
        String[] tags = new String[0];
        Matcher mat = patTitle.matcher(getVideoInfo);
        if (mat.find()) {
            title = URLDecoder.decode(mat.group(1), "UTF-8");
        }

        mat = patHlsvp.matcher(getVideoInfo);
        if(mat.find())
            isLiveStream = true;

        mat = patAuthor.matcher(getVideoInfo);
        if (mat.find()) {
            author = URLDecoder.decode(mat.group(1), "UTF-8");
        }
        mat = patChannelId.matcher(getVideoInfo);
        if (mat.find()) {
            channelId = mat.group(1);
        }
        mat = patLength.matcher(getVideoInfo);
        if (mat.find()) {
            length = Long.parseLong(mat.group(1));
        }
        mat = patViewCount.matcher(getVideoInfo);
        if (mat.find()) {
            viewCount = Long.parseLong(mat.group(1));
        }
        mat = patAvgRating.matcher(getVideoInfo);
        if (mat.find()) {
        	avgRating = Double.parseDouble(mat.group(1).replace('&', '\u0000'));
        }
        mat = patTags.matcher(getVideoInfo);
        if (mat.find()) {
        	final String g0 = mat.group();
        	final Charset chars = CharsetUtil.charset(g0);
        	tags = URLDecoder.decode(g0, chars.name()).replaceAll(Pattern.quote("+"), " ").split(",");
        }
        return new VideoMeta(videoID, title, author, channelId, length, viewCount, isLiveStream, avgRating,
        		parseEndVideoData(videoID, author), tags, getEndScreen(videoID, author), false, null, Json.array());
	}
	
	public static class EndVideoData {
		private final long subCount;
		private final String imageURL;
		private final DataSet<Integer, Integer> imageDim;
		
		public EndVideoData(final long subCount,
				final String imageURL, final DataSet<Integer, Integer> dim) {
			this.subCount = subCount;
			this.imageURL = imageURL;
			this.imageDim = dim;
		}
		
		public long getSubCount() {
			return this.subCount;
		}
		
		public String getImageURL() {
			return this.imageURL;
		}
		
		public DataSet<Integer, Integer> getImageDim() {
			return this.imageDim; 
		}
		
		public int getImageWidth() {
			return this.imageDim.getA();
		}
		
		public int getImageHeight() {
			return this.imageDim.getB();
		}
	}
	
	public static EndVideoData parseEndVideoData(final String videoID, final String _channelName) {
		try {
			// returns xml
			final String url = 
					"https://www.youtube.com/annotations_invideo?cap_hist=1&video_id=" + videoID + "&client=1";
			//final JsonValue json = Json.parse(URIUtil.downloadPage(URIUtil.safeCreate(url)));
			final String pageData = URIUtil2.IO.send(URIUtil.safeCreate(url)).getA(); //URIUtil.downloadPage(URIUtil.safeCreate(url));
			if (pageData.equals("failed")) {
				return new EndVideoData(-1, "", new DataSet<Integer, Integer>(0, 0));
			}
			final XML.Doc doc = XML.doc(pageData);
			//ArrayUtils.printLln(doc.w3c().getElementsByTagName("data"));
			final NodeList x = doc.w3c().getElementsByTagName("data");
			for (int i = 0; i < x.getLength(); i++) {
				final Node n = x.item(i);
				try {
					final JsonValue js0 = Json.parse(n.getTextContent().replaceAll(Pattern.quote("&quot;"), "'"));
					if (js0.asObject().hasValue("num_subscribers") && js0.asObject().getString("channel_name", "").equals(_channelName)) {
						return new EndVideoData(
								js0.asObject().getLong("num_subscribers", -1), 
								js0.asObject().getString("image_url", ""), 
								new DataSet<Integer, Integer>(js0.asObject().getInt("image_width", 0),
										js0.asObject().getInt("image_height", 0)));
					}
				}catch (Exception e) {
					e.printStackTrace();
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
			return new EndVideoData(-1, "", new DataSet<Integer, Integer>(0, 0));
		}
		return new EndVideoData(-1, "", new DataSet<Integer, Integer>(0, 0));
	}
	
	private static final String ENDSCREEN_PLAYLIST = "PLAYLIST";
	private static final String ENDSCREEN_VIDEO = "VIDEO";
	private static final String ENDSCREEN_CHANNEL = "CHANNEL";
	
	public static class EndscreenVideo {
		
		private String title;
		private String url;
		private String imageURL;
		private int imageWidth;
		private int imageHeight;
		private long length;
		
		public EndscreenVideo(final String title, final String url, final String imageURL,
				final int imageWidth, final int imageHeight, final long length) {
			this.title = title;
			this.url = url;
			this.imageURL = imageURL;
			this.imageWidth = imageWidth;
			this.imageHeight = imageHeight;
		}
		
		@Override
		public String toString() {
			return "EndscreenVideo[title=" + this.title + "]";
		}
		
		public String getTitle() {
			return this.title;
		}
		
		public String getURL() {
			return this.url;
		}
		
		public String getImageURL() {
			return this.imageURL;
		}
	}
	
	public static DataSet<LinkedList<EndscreenVideo>, Long> getEndScreen(final String videoID, final String _channelName) {
		try {
			final String url = "https://www.youtube.com/get_endscreen?client=1&v=" + videoID + "&ptype=embedded";
			String pageData = URIUtil2.IO.send(URIUtil.safeCreate(url)).getA();
			if (pageData.equals("failed")) {
				return new DataSet<LinkedList<EndscreenVideo>, Long>(new LinkedList<EndscreenVideo>(), -1L);
			}
			if (pageData.startsWith(")]}")) {
				pageData = pageData.substring(3);
			}
			if (pageData.equals("\"\"")) {
				return new DataSet<LinkedList<EndscreenVideo>, Long>(new LinkedList<EndscreenVideo>(), -1L);
			}
			long subs = -1;
			final LinkedList<EndscreenVideo> videos = new LinkedList<EndscreenVideo>();
			final JsonObject jso = Json.parse(pageData).asObject();
			final JsonArray elements = jso.get("elements").asArray();
			for (final JsonValue vx : elements) {
				final JsonValue v = vx.asObject().get("endscreenElementRenderer");
				final String type = v.asObject().getString("style", "N/A");
				if (type.equals(ENDSCREEN_VIDEO)) {
					final JsonValue thumbnails = v.asObject().get("image").asObject().get("thumbnails").asArray().get(0);
					final String imageURL = thumbnails.asObject().getString("url", "");
					final String title = v.asObject().get("title").asObject().getString("simpleText", "N/A");
					final String ul = v.asObject().get("endpoint").asObject().get("urlEndpoint").asObject().getString("url", "");
					final long length = 0;
					videos.add(new EndscreenVideo(title, ul, imageURL, 400, 300, length));
				}else if (type.equals(ENDSCREEN_CHANNEL)) {
					final String channelName = v.asObject().get("title").asObject().getString("simpleText", "___failed___");
					if (!channelName.equals("___failed___") && channelName.equals(_channelName)) {
						try {
							final JsonValue p0 = v.asObject().get("hovercardButton");
							final JsonValue r0 = p0
									.asObject().get("subscribeButtonRenderer").asObject().get("shortSubscriberCountText");
							final JsonValue r = r0.asObject().get("runs");
							subs = r
									.asArray().get(0).asObject().getInt("text", -1);
						}catch (Exception e) {
						}
					}
				}else {
					
				}
			}
			return new DataSet<LinkedList<EndscreenVideo>, Long>(videos, subs);
		}catch (Exception e) {
			e.printStackTrace();
		}
		return new DataSet<LinkedList<EndscreenVideo>, Long>(new LinkedList<EndscreenVideo>(), -1L);
	}
}
